package nl.weeaboo.sh.danmaku;

import java.awt.Rectangle;
import java.util.Collection;

import nl.weeaboo.sh.objects.Sprite;

public class ColGrid {

	public static final int gridSize = 16;	
	
	public enum ColRectMode {
		HIT, DAMAGE, SPLASH;
	};
	
	public ColList cells[][][];    //Uniform grid for small objects.
	public ColList largeObjects[]; //Keep objects that are larger than 1 grid cell separate.
	
	private int w, h;
	private int types;
	
	public ColGrid(int w, int h, int types) {
		this.w = w;
		this.h = h;
		this.types = types;
		
		cells = new ColList[w][h][types];
		for (int x = 0; x < w; x++) {
			for (int y = 0; y < h; y++) {
				for (int t = 0; t < types; t++) {
					cells[x][y][t] = new ColList();
				}
			}
		}
		
		largeObjects = new ColList[types];
		for (int t = 0; t < types; t++) {
			largeObjects[t] = new ColList();
		}
	}
	
	//Functions
	public void collide(Collection<ShotModel> shots) {
		for (ShotModel shot : shots) {
			for (int n = 0; n < shot.colNodesL; n++) {
				ColNode node = shot.colNodes[n];
				
				int amin, amax, bmin, bmax, cmin;
				if (node.radius > gridSize) {
					amin = toGridCoord(node.parent.x+node.x-node.radius)   - 1;
					amax = toGridCoord(node.parent.x+node.x+node.radius-1) + 1;
					bmin = toGridCoord(node.parent.y+node.y-node.radius)   - 1;
					bmax = toGridCoord(node.parent.y+node.y+node.radius-1) + 1;
					cmin = 0;
					
					//Large <-> Large Collisions
					for (int c = node.type+1; c < types; c++) {
						ColNode current = largeObjects[c].first;
						while (current != null) {
							collide(node, current);
							
							if (shot.killed) {
								c = types+1;
								break;
							}
							current = current.next;
						}						
					}
				} else {
					amin = node.gx-1; 
					amax = node.gx+1; 
					bmin = node.gy-1; 
					bmax = node.gy+1; 
					cmin = node.type+1;
				}
				
				if (amin < 0)   amin = 0;
				if (amax > w-1) amax = w-1;
				if (bmin < 0)   bmin = 0;
				if (bmax > h-1) bmax = h-1;
				
				//Large/Small <-> Small Collisions
				for (int a = amin; a <= amax; a++) {
					for (int b = bmin; b <= bmax; b++) {
						for (int c = cmin; c < types; c++) {
							if (c == node.type) continue;
							
							ColNode current = cells[a][b][c].first;
							while (current != null) {
								collide(node, current);
								
								if (shot.killed) {
									a = amax+1;
									b = bmax+1;
									c = types+1;
									break;
								}
								current = current.next;
							}
						}
					}
				}
			}
		}
	}
	
	protected void collide(ColNode a, ColNode b) {
		if (!a.parent.visible || !b.parent.visible) return;
		
		float ax = a.parent.x+a.x;
		float ay = a.parent.y+a.y;
		float bx = b.parent.x+b.x;
		float by = b.parent.y+b.y;
		
		int dx = (ax >= bx ? (int)(ax-bx) : (int)(bx-ax)); //abs(round(x2-x1))
		int dy = (ay >= by ? (int)(ay-by) : (int)(by-ay));
		
		int r = a.radius + b.radius;
		if (dx < r && dy < r && dx*dx+dy*dy < a.radiusSq+b.radiusSq) {
			int ap = a.parent.getPower();
			int bp = b.parent.getPower();
			
			a.parent.damage(bp);
			b.parent.damage(ap);
		}
	}
	
	public void collideRect(SpriteShotColHandler handler, Sprite s, ColRectMode m,
			Rectangle r)
	{
		int amin = toGridCoord(r.x)-1;            if (amin < 0)   amin = 0;
		int amax = toGridCoord(r.x+r.width-1)+1;  if (amax > w-1) amax = w-1;
		int bmin = toGridCoord(r.y)-1;            if (bmin < 0)   bmin = 0;
		int bmax = toGridCoord(r.y+r.height-1)+1; if (bmax > h-1) bmax = h-1;
		
		//Rect <-> Small Collisions
		for (int a = amin; a <= amax; a++) {
			for (int b = bmin; b <= bmax; b++) {
				for (int c = 0; c < types; c++) {
					ColNode current = cells[a][b][c].first;
					while (current != null) {
						collideRect(handler, s, m, r, current);						
						current = current.next;
					}
				}
			}
		}
		
		//Rect <-> Large Collisions
		for (int c = 0; c < types; c++) {
			ColNode current = largeObjects[c].first;
			while (current != null) {
				collideRect(handler, s, m, r, current);						
				current = current.next;
			}
		}
	}
	
	protected void collideRect(SpriteShotColHandler handler, Sprite s, ColRectMode m,
			Rectangle a, ColNode cn)
	{
		if (!cn.parent.visible) return;
		
		Rectangle b = new Rectangle(
				Math.round(cn.parent.x+cn.x-cn.radius),
				Math.round(cn.parent.y+cn.y-cn.radius),
				cn.radius<<1, cn.radius<<1);
		
		if (a.intersects(b)) {			
			handler.onCollision(s, m, cn, a, b);
		}		
	}
		
	public void remove(ShotModel shot) {
		for (int n = 0; n < shot.colNodesL; n++) {
			shot.colNodes[n].remove();
		}
	}
	public void updateNodeCell(ColNode node) {
		if (node.radius > gridSize) {
			node.gx = toGridCoord(node.parent.x+node.x);
			node.gy = toGridCoord(node.parent.y+node.y);
			if (node.list == null) {
				largeObjects[node.type].add(node);
			}
		} else {
			int gx = toGridCoord(node.parent.x+node.x);
			int gy = toGridCoord(node.parent.y+node.y);			
			if (node.gx != gx || node.gy != gy) {
				if (node.list != null) {
					node.remove();
				}

				node.gx = gx;
				node.gy = gy;

				if (gx >= 0 && gx < w && gy >= 0 && gy < h) {
					cells[gx][gy][node.type].add(node);
				}				
			}
		}
	}	
	
	//Getters
	public int toGridCoord(float x) { return ((int)(x >= 0 ? x+.5f : x-.5f))>>4; }
	public int toGridCoord(  int x) { return x>>4; }
	public int fromGridCoord(int x) { return x<<4; }

	//Setters

}
